package org.zoomdev.zoom.dao.impl;

import org.zoomdev.zoom.common.expression.Symbol;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.Select;
import org.zoomdev.zoom.dao.Sql;
import org.zoomdev.zoom.dao.Where;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

@SuppressWarnings("ALL")
public class SimpleSelect implements Select<Select> {

    //private static final Log log = LogFactory.getLog(SimpleSelect.class);

    protected StringBuilder where;
    protected StringBuilder orderBy;
    protected StringBuilder groupBy;
    protected StringBuilder join;
    protected StringBuilder select;
    protected List<Object> values;
    protected StringBuilder having;

    protected boolean condition;
    int limit;
    int position;
    String table;
    private int index = 0;

    public SimpleSelect() {
        where = new StringBuilder();
        orderBy = new StringBuilder();
        join = new StringBuilder();
        groupBy = new StringBuilder();
        select = new StringBuilder();
        having = new StringBuilder();
        values = new ArrayList<>();
    }


    public String buildSelect() {
        StringBuilder sb = new StringBuilder();
        return buildSelect(sb).toString();
    }

    public StringBuilder buildSelect(StringBuilder sql) {
        sql.append("SELECT");
        if (select.length() == 0) {
            sql.append(" *");
        } else {
            sql.append(" ").append(select);
        }
        sql.append(" FROM ").append(table);
        return buildQuery(sql);
    }

    public StringBuilder buildSelectForCount(StringBuilder sql) {
        sql.append("SELECT COUNT(*) FROM ").append(table);
        return buildWhere(sql);
    }


    public StringBuilder buildQuery(StringBuilder sql) {
        sql.append(join);
        if (where.length() > 0) {
            sql.append(" WHERE ");
        }
        sql.append(where);
        sql.append(groupBy);

        if (having.length() > 0) {
            sql.append(" HAVING ").append(having);
        }
        sql.append(orderBy);
        return sql;
    }

    public StringBuilder buildWhere(StringBuilder sql) {
        if (where.length() > 0) {
            sql.append(" WHERE ");
        }
        return sql;
    }

    public StringBuilder buildDelete(StringBuilder sql) {
        if (where.length() <= 0) {
            throw new DaoException("Whole table delete is not valid!");
        }
        sql.append("DELETE FROM ").append(table);
        if (where.length() > 0) {
            sql.append(" WHERE ").append(where);
        }
        return sql;

    }

    public void clear() {
        where.setLength(0);
        join.setLength(0);
        groupBy.setLength(0);
        values.clear();
        having.setLength(0);
        orderBy.setLength(0);
        select.setLength(0);
    }

    private void addValue(Object value) {
        this.values.add(value);
    }

    private void whereRelation(StringBuilder where, String relation) {
        if (condition) {
            condition = false;
        } else {
            if (where.length() == 0) {
            } else {
                where.append(relation);
            }
        }
    }

    public Select like(String key, Like like, Object value) {
        return relationLike(key, like, value, AND, false);
    }

    public Select orWhere(Condition condition) {
        return orWhere(this, condition);
    }

    private Select relationLike(String key, Like like, Object value, String relation, boolean not) {
        assert (key != null);

        checkValue(key, value);
        whereRelation(where, relation);

        where.append(key);
        if (not) {
            where.append(" NOT");
        }
        where.append(" LIKE ?");
        addValue(like.toValue(value));
        return this;
    }

    public Select orLike(String key, Like like, Object value) {
        return relationLike(key, like, value, OR, false);
    }

    private void checkValue(String key, Object value) {
        if (value == null) {
            throw new DaoException(String.format("名称为%s的字段值为null,请使用whereNull或者whereNotNull版本", key));
        }
    }

    public Select where(String key, Symbol symbol, Object value) {
        assert (key != null && symbol != null);
        return whereImpl(where, key, symbol, value, AND);
    }

    public Select where(Condition condition) {
        return where(this, condition);
    }

    public Select where(Where<?> sql, Condition condition) {
        whereRelation(where, AND);
        conditionWhere(sql, condition);
        return this;
    }

    protected Select whereImpl(StringBuilder where, String key, Symbol symbol, Object value, String relation) {
        checkValue(key, value);
        whereRelation(where, relation);
        where.append(key).append(symbol.value());
        if (value instanceof Sql.PlaceHolder) {
            Sql.PlaceHolder placeHolder = (Sql.PlaceHolder) value;
            where.append(placeHolder.value);
            Collections.addAll(values, placeHolder.args);
        } else {
            where.append("?");
            addValue(value);
        }


        return this;
    }

    public Select where(String key, Object value) {
        return whereImpl(where, key, Symbol.EQ, value, AND);
    }

    public Select orWhere(String key, Object value) {
        return orWhere(key, Symbol.EQ, value);
    }

    public Select orWhere(Where<?> sql, Condition condition) {
        whereRelation(where, OR);
        conditionWhere(this, condition);
        return this;
    }

    void conditionWhere(Where<?> sql, Condition condition) {
        where.append("(");
        this.condition = true;
        condition.where(sql);
        if (this.condition) {
            throw new DaoException("Condition下至少需要一个条件");
        }
        where.append(")");
    }

    public Select orWhere(String key, Symbol symbol, Object value) {
        return whereImpl(where, key, symbol, value, OR);
    }

    public Select whereNull(String key) {
        return whereNull(key, AND, false);
    }

    private Select whereNull(String key, String relation, boolean not) {

        StringBuilder where = this.where;
        whereRelation(where, relation);
        where.append(key);
        where.append(" IS ");
        if (not) where.append("NOT ");
        where.append("NULL");

        return this;
    }

    public Select whereNotNull(String key) {
        return whereNull(key, AND, true);
    }

    public <E> Select whereNotIn(String key, Collection<E> values) {
        return whereIn(key, AND, true, values);
    }

    protected <E> Select whereIn(String key, String relation, boolean not, Collection<E> values) {
        StringBuilder where = this.where;
        whereRelation(where, relation);
        where.append(key);
        if (not) {
            where.append(" NOT");
        }
        where.append(" IN (");
        boolean first = true;
        for (Object object : values) {
            if (first) {
                first = false;
            } else {
                where.append(",");
            }
            where.append("?");
            this.addValue(object);
        }
        where.append(')');

        return this;
    }

    public <E> Select whereIn(String key, Collection<E> values) {
        return whereIn(key, AND, false, values);
    }

    public Select limit(int limit) {
        return limit(0, limit);
    }

    public Select limit(int position, int limit) {
        this.position = position;
        this.limit = limit;
        return this;
    }

    public Select orderBy(String key, Sort sort) {
        assert (sort != null && key != null);
        if (orderBy.length() != 0) {
            orderBy.append(',');
        } else {
            orderBy.append(" ORDER BY ");
        }
        orderBy.append(key).append(SPACE).append(sort.value());
        return this;
    }

    public Select groupBy(String key) {
        groupBy.append(" GROUP BY ").append(key);
        return this;
    }

    public Select having(String key, Symbol symbol, Object value) {
        return whereImpl(having, key, symbol, value, AND);
    }

    public Select join(String otherTable, String on) {
        return join(otherTable, on, JoinType.INNER);
    }

    public Select join(String table, String on, JoinType type) {
        join.append(SPACE)
                .append(type.value())
                .append(" JOIN ")
                .append(table).append(" ON ").append(on);

        return this;
    }

    public Select select(String select) {
        if (this.select.length() != 0) {
            this.select.append(COMMA);
        }
        this.select.append(select);
        return this;
    }


    public Select from(String table) {
        this.table = table;
        return this;
    }


    public Object[] getArgs() {
        return this.values.toArray(new Object[values.size()]);
    }
}
